#pragma once

#include <iostream>
#include <queue>
#include <vector>
#include <unistd.h>
#include <string>
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Thread.hpp"

const static int gthreadnum = 3;

template <class T>
class ThreadPool
{
private:
    bool QueueIsEmpty()
    {
        return _q.empty();
    }
    void Routine(const std::string &name) // 想要让这个函数能够知道线程名 -> 通过参数的形式传递进来 -> 修改Thread 中的func_t
    {
        while (true) // 只要用户没有取消线程池，线程就需要一直从任务队列中获取任务来执行
        {
            T t; // 将获取的任务作为线程私有的
            // 从任务队列中获取任务，需要加锁
            {
                LockGuard lockguard(&_lock);
                // 判断任务队列中是否有数据，没有就去条件变量下等待
                // 四种情况: 空，运行  空，退出  非空，运行 非空，退出
                // 只有当任务队列为空并且线程池的状态为运行，线程才可以到条件变量下等待
                while (QueueIsEmpty() && _is_running) // 因为存在伪唤醒的情况，所以此处需要使用while
                {
                    _wait_thread_num++;
                    _cond.Wait(_lock); // 需要传锁过去
                    _wait_thread_num--;
                }
                // 空，退出  非空，运行 非空，退出
                if (QueueIsEmpty() && !_is_running)
                {
                    // 状态为退出且没有任务需要执行，直接退出即可
                    LOG(LogLevel::INFO) << name << " 线程池退出 && 任务队列为空";
                    break; // 线程退出Routine 函数，之后就会退出
                }
                // 非空，运行 非空，退出 --> 禁止让用户往任务队列中push 任务，而所有线程均被唤醒了那么
                // 任务队列中的任务会被线程执行完，最后任务队列会变为空
                t = _q.front();
                _q.pop();
            }
            t(); // 不在临界区中执行任务
            // LOG(LogLevel::INFO) << name << " 执行任务：" << t.Result2String();
        }
    }

    // 构造
    //_q 中存放任务，需要用户自己放入所要执行的任务，不用初始化；_threads 中存放线程，构造时可以创建好Thread对象放入_threads中
    //_mutex _cond 有自己的构造函数，会自动调用 _is_running 初始化为false;
    ThreadPool(int threadnum = gthreadnum) : _threadnum(threadnum), _is_running(false)
    {
        // 创建Thread 对象放入_threads中
        for (int i = 0; i < _threadnum; i++)
        {
            // 需要传递回调函数（任务）供给线程去执行,让线程执行ThreadPool 中的成员方法
            // hello 函数为成员函数，带有this 指针，想要将其传递过去:lanbda 或者bind
            std::string name = "thread-" + std::to_string(i + 1);
            Thread t([this](const std::string &name)
                     { this->Routine(name); }, name);

            // 并将t 放入_threads 当中，因为t 为临时对象，为了减少拷贝，可以使用右值引用
            _threads.push_back(std::move(t));
        }

        LOG(LogLevel::INFO) << "创建线程池对象成功";
    }

    // 禁用赋值、拷贝构造
    ThreadPool(const ThreadPool<T> &) = delete;
    ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

public:
    // 获取单例的函数
    static ThreadPool<T> *GetInstance()
    {
        {
            if (!_instance) // 双重判断，可以提高效率，而不用频繁地去申请锁
            {
                // 加锁
                LockGuard lockguard(&_singleton_lock);
                // 只创建一次
                if (!_instance)
                {
                    _instance = new ThreadPool<T>(5); // 只有在类中定义才可以调用私有的构造函数
                    LOG(LogLevel::INFO) << "第一次创建单例成功";
                    // 创建线程池
                    _instance->Start();
                }
            }
            else
            {
                LOG(LogLevel::INFO) << "单例已经存在，直接获取";
            }
        }
        // 直接返回
        return _instance;
    }
    void Start() // 真正意义上的创建线程
    {
        // 检查标志位，为避免重复创建
        if (_is_running)
            return;
        _is_running = true;

        // Thread 中有现成的方法，只需要遍历 _threads 并调用Thread 当中的Wait 方法就可以了
        for (auto &t : _threads)
        {
            t.Start();
        }
        LOG(LogLevel::INFO) << "创建线程池成功";
    }
    // 取消线程池 -> 需要在Thread 提供接口来取消现成，然后在此处就仅需遍历+调用
    void Stop()
    {
        // 取消线程池的核心思想：如果条件变量下有等待的线程，唤醒所有的线程，让没有执行完的任务执行完，然后再退出
        // 并且不再向任务队列中push 任务
        // 如果线程池本身就是退出状态直接返回
        if (!_is_running)
            return;
        _is_running = false;
        if (_wait_thread_num)
            _cond.NotifyAll();

        // 以下取消线程池的操作过于简单粗暴
        //  if (!_is_running)
        //      return;
        //  for (auto &t : _threads)
        //  {
        //      t.Stop();
        //  }
        //  _is_running = false;
        //  LOG(LogLevel::INFO) << "取消线程池成功";
    }
    // 等待线程池中的所有线程
    void Wait()
    {
        for (auto &t : _threads)
        {
            t.Join();
        }
        LOG(LogLevel::INFO) << "等待线程池成功";
    }
    void Enqueue(const T &in)
    {
        if (_is_running == false)
            return; // 此处可能会存在时序问题
        // 也是在访问临界资源，需要加锁
        {
            LockGuard lockguard(&_lock);
            _q.push(in);
            // 判断在条件变量下是否有等待的线程，有的话就进行唤醒
            if (_wait_thread_num)
                _cond.NotifyOne();
        }
    }
    ~ThreadPool()
    {
    }

private:
    // 任务队列 —— 存放任务 -》共享资源，需要互斥与同步，一个条件变量便可
    std::queue<T> _q;
    // 对线程进行管理的数据结构 -> 使用vector、map 等均可
    std::vector<Thread> _threads;
    int _threadnum;       // 所要创建线程的个数
    int _wait_thread_num; // 记录在条件变量下等待的线程数量

    // 互斥与同步
    Mutex _lock;
    Cond _cond;

    // 一个标志位，来表示该线程池是否启动
    bool _is_running;

    // 单例- 懒汉模式 --> 将构造函数设置为私有,禁用拷贝构造、赋值重载
    static ThreadPool<T> *_instance; // 函数封装获取单例
    // 由于存在多个线程访问同一个线程池的可能性，那么 _instance就为临界资源,故而需要加锁
    static Mutex _singleton_lock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_instance = nullptr;

template <class T>
Mutex ThreadPool<T>::_singleton_lock;